package com.jfeinstein.jazzyviewpager;

import java.util.HashMap;
import java.util.LinkedHashMap;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.PageSlider;
import ohos.agp.render.ThreeDimView;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

/**
 * JazzyViewPager
 */
public class JazzyViewPager extends PageSlider {
    /**
     * sOutlineColor
     */
    public static Color sOutlineColor = Color.WHITE;
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x002A, "JazzyViewPager");

    private static final String EFFECT = "effect";
    private static final String FADE = "fadeEnabled";

    private static final float SCALE_MAX = 0.5f;
    private static final float ROT_MAX = 15.0f;
    private static final float ZOOM_MAX = 0.5f;

    private boolean mEnabled = true;
    private boolean mFadeEnabled = false;
    private boolean mOutlineEnabled = false;

    private TransitionEffect mEffect = TransitionEffect.Standard;

    private HashMap<Integer, Object> mObjs = new LinkedHashMap<Integer, Object>();

    /**
     * TransitionEffect
     */
    public enum TransitionEffect {
        Standard,
        Tablet,
        CubeIn,
        CubeOut,
        FlipVertical,
        FlipHorizontal,
        Stack,
        ZoomIn,
        ZoomOut,
        RotateUp,
        RotateDown,
        Accordion
    }

    private String [] jazzEffects = {"Standard",
            "Tablet",
            "CubeIn",
            "CubeOut",
            "FlipVertical",
            "FlipHorizontal",
            "Stack",
            "ZoomIn",
            "ZoomOut",
            "RotateUp",
            "RotateDown",
            "Accordion"};

    public JazzyViewPager(Context context) {
        this(context, null);
    }

    @SuppressWarnings("incomplete-switch")
    public JazzyViewPager(Context context, AttrSet attrs) {
        super(context, attrs);
        int effect = 0;
        boolean fadeEnable = false;
        setOutlineEnabled(false);

        if (attrs != null) {
            effect = attrs.getAttr(EFFECT).isPresent()
                    ? attrs.getAttr(EFFECT).get().getIntegerValue() : 0;
            fadeEnable = attrs.getAttr(FADE).isPresent() && attrs.getAttr(FADE).get().getBoolValue();
        }

        setTransitionEffect(TransitionEffect.valueOf(jazzEffects[effect]));
        setFadeEnabled(fadeEnable);

        initListeners();
    }

    private void initListeners() {
        addPageChangedListener(new PageChangedListener() {
            @Override
            public void onPageSliding(int position, float positionOffset, int positionOffsetPixels) {
                onPageScrolled(position, positionOffset, positionOffsetPixels);
            }

            @Override
            public void onPageSlideStateChanged(int index) {
            }

            @Override
            public void onPageChosen(int index) {
            }
        });
    }

    /**
     * setTransitionEffect
     * @param effect effect
     */
    public void setTransitionEffect(TransitionEffect effect) {
        mEffect = effect;
    }

    /**
     * setPagingEnabled
     * @param enabled flag
     */
    public void setPagingEnabled(boolean enabled) {
        mEnabled = enabled;
    }

    /**
     * setFadeEnabled
     * @param enabled flag
     */
    public void setFadeEnabled(boolean enabled) {
        mFadeEnabled = enabled;
    }

    /**
     * getFadeEnabled
     * @return flag
     */
    public boolean getFadeEnabled() {
        return mFadeEnabled;
    }

    /**
     * setOutlineEnabled
     * @param enabled flag
     */
    public void setOutlineEnabled(boolean enabled) {
        mOutlineEnabled = false;
        wrapWithOutlines();
    }

    /**
     * setOutlineColor
     * @param color color
     */
    public void setOutlineColor(Color color) {
        sOutlineColor = color;
    }

    private void wrapWithOutlines() {
        for (int i = 0; i < getChildCount(); i++) {
            Component view = getComponentAt(i);
            if (!(view instanceof OutlineContainer)) {
                removeComponent(view);
                super.addComponent(wrapChild(view), i);
            }
        }
    }

    private Component wrapChild(Component child) {
        if (!mOutlineEnabled || child instanceof OutlineContainer) {
            return child;
        }
        OutlineContainer out = new OutlineContainer(getContext());
        out.setLayoutConfig(getLayoutConfig());
        child.setLayoutConfig(new OutlineContainer.LayoutConfig(
                LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
        out.addComponent(child);
        return out;
    }

    /**
     * addView
     * @param child child
     */
    public void addView(Component child) {
        super.addComponent(wrapChild(child));
    }

    /**
     * addView
     * @param child child
     * @param index index
     */
    public void addView(Component child, int index) {
        super.addComponent(wrapChild(child), index);
    }

    /**
     * addView
     * @param child child
     * @param params LayoutConfig
     */
    public void addView(Component child, LayoutConfig params) {
        super.addComponent(wrapChild(child), params);
    }

    /**
     * addView
     * @param child child
     * @param width width
     * @param height width
     */
    public void addView(Component child, int width, int height) {
        super.addComponent(wrapChild(child), width, height);
    }

    /**
     * addView
     * @param child child
     * @param index index
     * @param params LayoutConfig
     */
    public void addView(Component child, int index, LayoutConfig params) {
        super.addComponent(wrapChild(child), index, params);
    }

    private Component mCurrent;
    private Component mLeft;
    private Component mRight;
    private float mRot;
    private float mScale;
    private State mState;
    private float mTrans;

    private enum State {
        IDLE,
        GOING_LEFT,
        GOING_RIGHT
    }

    private void logState(Component veiw, String title) {
        HiLog.info(LABEL, title + ": ROT (" + veiw.getRotation() + ", " +
                /* v.getRotationX() + ", " +
                veiw.getRotationY() + "), TRANS (" + */
                veiw.getTranslationX() + ", " +
                veiw.getTranslationY() + "), SCALE (" +
                veiw.getScaleX() + ", " +
                veiw.getScaleY() + "), ALPHA " +
                veiw.getAlpha());
    }

    /**
     * animateScroll
     * @param position index
     * @param positionOffset positionOffset
     */
    protected void animateScroll(int position, float positionOffset) {
        if (mState != State.IDLE) {
            mRot = (float)(1 - Math.cos(2 * Math.PI * positionOffset)) / 2 * 30.0f;
            // setRotationY(mState == State.GOING_RIGHT ? mRot : -mRot);
            setPivotX(getEstimatedWidth()*0.5f);
            setPivotY(getEstimatedHeight()*0.5f);
        }
    }

    /**
     * animateTablet
     * @param left child
     * @param current child
     * @param right child
     * @param positionOffset positionOffset
     */
    protected void animateTablet(Component left, Component current, Component right, float positionOffset) {
        if (mState != State.IDLE) {
            if (current != null) {
                mRot = 30.0f * positionOffset;
                mTrans = getOffsetXForRotation(mRot, current.getEstimatedWidth(),
                        current.getEstimatedHeight());
                current.setPivotX(current.getEstimatedWidth()/2);
                current.setPivotY(current.getEstimatedHeight()/2);
                current.setTranslationX(mTrans);
                // current.setRotationY(mRot);
            }
            if (right != null) {
                mRot = -30.0f * (1 - positionOffset);
                mTrans = getOffsetXForRotation(mRot, right.getEstimatedWidth(), right.getEstimatedHeight());
                right.setPivotX(right.getEstimatedWidth() * 0.5f);
                right.setPivotY(right.getEstimatedHeight() * 0.5f);
                right.setTranslationX(mTrans);
                // right.setRotationY(mRot);
            }
        }
    }

    private void animateCube(Component left, Component current, Component right, float positionOffset, boolean in) {
        if (mState != State.IDLE) {
            if (current != null) {
                mRot = (in ? 90.0f : -90.0f) * positionOffset;
                current.setPivotX(current.getEstimatedWidth());
                current.setPivotY(current.getEstimatedHeight()*0.5f);
                // current.setRotationY(mRot);
            }
            if (right != null) {
                mRot = -(in ? 90.0f : -90.0f) * (1 - positionOffset);
                right.setPivotX(0);
                right.setPivotY(right.getEstimatedHeight()*0.5f);
                // right.setRotationY(mRot);
            }
        }
    }

    private void animateAccordion(Component left, Component current, Component right, float positionOffset) {
        if (mState == State.GOING_RIGHT) {
            if (current != null) {
                current.setPivotX(current.getEstimatedWidth());
                current.setPivotY(0);
                current.setScaleX(1 - positionOffset);
            }
            if (right != null) {
                right.setPivotX(0);
                right.setPivotY(0);
                right.setScaleX(positionOffset);
            }
        }
        if (mState == State.GOING_LEFT) {
            if (current != null) {
                current.setPivotX(0);
                current.setPivotY(current.getEstimatedHeight());
                current.setScaleX(1 - positionOffset);
            }
            if (left != null) {
                left.setPivotX(left.getEstimatedWidth());
                left.setPivotY(left.getEstimatedHeight());
                left.setScaleX(positionOffset);
            }
        }
    }

    private void animateZoom(Component left, Component current, Component right, float positionOffset, boolean in) {
        if (mState == State.GOING_RIGHT) {
            if (current != null) {
                mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * (1 - positionOffset) :
                        1 + ZOOM_MAX - ZOOM_MAX * (1 - positionOffset);
                current.setPivotX(current.getEstimatedWidth() * 0.5f);
                current.setPivotY(current.getEstimatedHeight() * 0.5f);
                current.setScaleX(mScale);
                current.setScaleY(mScale);
            }
            if (right != null) {
                mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * positionOffset :
                        1 + ZOOM_MAX - ZOOM_MAX * positionOffset;
                right.setPivotX(right.getEstimatedWidth() * 0.5f);
                right.setPivotY(right.getEstimatedHeight() * 0.5f);
                right.setScaleX(mScale);
                right.setScaleY(mScale);
            }
        }
        if (mState == State.GOING_LEFT) {
            if (left != null) {
                mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * positionOffset :
                        1 + ZOOM_MAX - ZOOM_MAX * positionOffset;
                left.setPivotX(left.getEstimatedWidth() * 0.5f);
                left.setPivotY(left.getEstimatedHeight() * 0.5f);
                left.setScaleX(mScale);
                left.setScaleY(mScale);
            }
            if (current != null) {
                mScale = in ? ZOOM_MAX + (1 - ZOOM_MAX) * (1 - positionOffset) :
                        1 + ZOOM_MAX - ZOOM_MAX * (1 - positionOffset);
                current.setPivotX(current.getEstimatedWidth() * 0.5f);
                current.setPivotY(current.getEstimatedHeight() * 0.5f);
                current.setScaleX(mScale);
                current.setScaleY(mScale);
            }
        }
    }

    private void animateRotate(Component left, Component current, Component right, float positionOffset, boolean up) {
        if (mState == State.GOING_RIGHT) {
            if (current != null) {
                mRot = (up ? 1 : -1) * (ROT_MAX * positionOffset);
                mTrans = (up ? -1 : 1) * (float) (getEstimatedHeight() - getEstimatedHeight()
                        * Math.cos(mRot * Math.PI / 180.0f));
                current.setPivotX(current.getEstimatedWidth() * 0.5f);
                current.setPivotY(up ? 0 : current.getEstimatedHeight());
                current.setTranslationY(mTrans);
                current.setRotation(mRot);
            }
            if (right != null) {
                mRot = (up ? 1 : -1) * (-ROT_MAX + ROT_MAX * positionOffset);
                mTrans = (up ? -1 : 1) * (float) (getEstimatedHeight() - getEstimatedHeight()
                        * Math.cos(mRot * Math.PI / 180.0f));
                right.setPivotX(right.getEstimatedWidth() * 0.5f);
                right.setPivotY(up ? 0 : right.getEstimatedHeight());
                right.setTranslationY(mTrans);
                right.setRotation(mRot);
            }
        }

        if (mState == State.GOING_LEFT) {
            if (left != null) {
                mRot = (up ? 1 : -1) * (ROT_MAX * (1 - positionOffset));
                mTrans = (up ? -1 : 1) * (float) (getEstimatedHeight() - getEstimatedHeight()
                        * Math.cos(mRot * Math.PI / 180.0f));
                left.setPivotX(left.getEstimatedWidth() * 0.5f);
                left.setPivotY(up ? 0 : current.getEstimatedHeight());
                left.setTranslationY(mTrans);
                left.setRotation(mRot);
            }
            if (current != null && right !=null) {
                mRot = (up ? 1 : -1) * (-ROT_MAX + ROT_MAX * (1 - positionOffset));
                mTrans = (up ? -1 : 1) * (float) (getEstimatedHeight() - getEstimatedHeight()
                        * Math.cos(mRot * Math.PI / 180.0f));
                current.setPivotX(right.getEstimatedWidth() * 0.5f);
                current.setPivotY(up ? 0 : right.getEstimatedHeight());
                current.setTranslationY(mTrans);
                current.setRotation(mRot);
            }
        }
    }

    private void animateFlipHorizontal(Component left, Component current, Component right, float positionOffset,
                            int positionOffsetPixels) {
        if (mState != State.IDLE) {
            if (current != null) {
                mRot = 180.0f * positionOffset;
                if (mRot > 90.0f) {
                    current.setVisibility(Component.INVISIBLE);
                } else {
                    if (current.getVisibility() == Component.INVISIBLE) {
                        current.setVisibility(Component.VISIBLE);
                    }
                    mTrans = positionOffsetPixels;
                    current.setPivotX(current.getEstimatedWidth()*0.5f);
                    current.setPivotY(current.getEstimatedHeight()*0.5f);
                    current.setTranslationX(mTrans);
                    // current.setRotationY(mRot);
                }
            }
            if (right != null) {
                mRot = -180.0f * (1 - positionOffset);
                if (mRot < -90.0f) {
                    right.setVisibility(Component.INVISIBLE);
                } else {
                    if (right.getVisibility() == Component.INVISIBLE) {
                        right.setVisibility(Component.VISIBLE);
                    }
                    mTrans = -getWidth() - getPageMargin() + positionOffsetPixels;
                    right.setPivotX(right.getEstimatedWidth()*0.5f);
                    right.setPivotY(right.getEstimatedHeight()*0.5f);
                    right.setTranslationX(mTrans);
                    // right.setRotationY(mRot);
                }
            }
        }
    }

    private void animateFlipVertical(Component left, Component current, Component right, float positionOffset,
                            int positionOffsetPixels) {
        if (mState != State.IDLE) {
            if (current != null) {
                mRot = 180.0f * positionOffset;
                if (mRot > 90.0f) {
                    current.setVisibility(Component.INVISIBLE);
                } else {
                    if (current.getVisibility() == Component.INVISIBLE) {
                        current.setVisibility(Component.VISIBLE);
                    }
                    mTrans = positionOffsetPixels;
                    current.setPivotX(current.getEstimatedWidth()*0.5f);
                    current.setPivotY(current.getEstimatedHeight()*0.5f);
                    current.setTranslationX(mTrans);
                    // current.setRotationX(mRot);
                }
            }
            if (right != null) {
                mRot = -180.0f * (1 - positionOffset);
                if (mRot < -90.0f) {
                    right.setVisibility(Component.INVISIBLE);
                } else {
                    if (right.getVisibility() == Component.INVISIBLE) {
                        right.setVisibility(Component.VISIBLE);
                    }
                    mTrans = - getWidth() - getPageMargin() + positionOffsetPixels;
                    right.setPivotX(right.getEstimatedWidth()*0.5f);
                    right.setPivotY(right.getEstimatedHeight()*0.5f);
                    right.setTranslationX(mTrans);
                    // right.setRotationX(mRot);
                }
            }
        }
    }

    /**
     * animateStack
     * @param left child
     * @param current child
     * @param right child
     * @param positionOffset positionOffset
     * @param positionOffsetPixels positionOffsetPixels
     */
    protected void animateStack(Component left, Component current, Component right,
                                float positionOffset, int positionOffsetPixels) {
        if (mState == State.GOING_RIGHT) {
            if (right != null) {
                mScale = (1 - SCALE_MAX) * positionOffset + SCALE_MAX;
                mTrans = -getWidth() - getPageMargin() + positionOffsetPixels;
                right.setScaleX(mScale);
                right.setScaleY(mScale);
                right.setTranslationX(mTrans);
            }
            if (current != null) {
                moveChildToFront(current);
            }
        }
        if (mState == State.GOING_LEFT) {
            if (left != null) {
                mScale = (1 - SCALE_MAX) * positionOffset + SCALE_MAX;
                mTrans = getWidth() + getPageMargin() - Math.abs(positionOffsetPixels);
                left.setScaleX(mScale);
                left.setScaleY(mScale);
                left.setTranslationX(mTrans);
            }
            if (current != null) {
                moveChildToFront(current);
            }
        }
    }

    private Matrix mMatrix = new Matrix();
    private ThreeDimView mCamera = new ThreeDimView();
    private float[] mTempFloat2 = new float[2];

    /**
     * getOffsetXForRotation
     * @param degrees degrees
     * @param width width
     * @param height height
     * @return offset
     */
    protected float getOffsetXForRotation(float degrees, int width, int height) {
        mMatrix.reset();
        mCamera.rotateY(Math.abs(degrees));
        mCamera.getMatrix(mMatrix);

        mMatrix.preTranslate(-width * 0.5f, -height * 0.5f);
        mMatrix.postTranslate(width * 0.5f, height * 0.5f);
        mTempFloat2[0] = width;
        mTempFloat2[1] = height;
        mMatrix.mapPoints(mTempFloat2);
        return (width - mTempFloat2[0]) * (degrees > 0.0f ? 1.0f : -1.0f);
    }

    /**
     * animateFade
     * @param left child
     * @param current child
     * @param right child
     * @param positionOffset positionOffset
     */
    protected void animateFade(Component left, Component current, Component right, float positionOffset) {
        if (mState == State.GOING_RIGHT) {
            if (current != null) {
                current.setAlpha(1 - positionOffset);
            }
            if (right != null) {
                right.setAlpha(positionOffset);
            }
        }
        if (mState == State.GOING_LEFT) {
            if (left != null) {
                left.setAlpha(positionOffset);
            }
            if (current != null) {
                current.setAlpha(1 - positionOffset);
            }
        }
    }

    /**
     * animateOutline
     * @param left child
     * @param current child
     * @param right child
     */
    protected void animateOutline(Component left, Component current, Component right) {
        if (!(current instanceof OutlineContainer)) {
            return;
        }
        if (mState != State.IDLE) {
            if (current != null) {
                ((OutlineContainer)current).setOutlineAlpha(1.0f);
            }
            if (right != null) {
                ((OutlineContainer)right).setOutlineAlpha(1.0f);
            }
        } else {
            if (current != null) {
                ((OutlineContainer) current).start();
            }
            if (right != null) {
                ((OutlineContainer) right).start();
            }
        }
    }

    /**
     * onPageScrolled
     * @param position index
     * @param positionOffset positionOffset
     * @param positionOffsetPixels positionOffsetPixels
     */
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        if (positionOffsetPixels < 0) {
            mState = State.GOING_LEFT;
        } else {
            mState = State.GOING_RIGHT;
        }

        float effectOffset = isSmall(positionOffset) ? 0 : positionOffset;

        if (position > 0) {
            mLeft = findViewFromObject(position - 1);
        }
        mCurrent = findViewFromObject(position);
        mRight = findViewFromObject(position + 1);

        if (mFadeEnabled) {
            animateFade(mLeft, mCurrent, mRight, effectOffset);
        }
        if (mOutlineEnabled) {
            animateOutline(mLeft, mCurrent, mRight);
        }

        switch (mEffect) {
        case Standard:
            break;
        case Tablet:
            animateTablet(mLeft, mCurrent, mRight, effectOffset);
            break;
        case CubeIn:
            animateCube(mLeft, mCurrent, mRight, effectOffset, true);
            break;
        case CubeOut:
            animateCube(mLeft, mCurrent, mRight, effectOffset, false);
            break;
        case FlipVertical:
            animateFlipVertical(mLeft, mCurrent, mRight, positionOffset, positionOffsetPixels);
            break;
        case FlipHorizontal:
            animateFlipHorizontal(mLeft, mCurrent, mRight, effectOffset, positionOffsetPixels);
            break;
        case Stack:
            animateStack(mLeft, mCurrent, mRight, effectOffset, positionOffsetPixels);
            break;
        case ZoomIn:
            animateZoom(mLeft, mCurrent, mRight, effectOffset, true);
            break;
        case ZoomOut:
            animateZoom(mLeft, mCurrent, mRight, effectOffset, false);
            break;
        case RotateUp:
            animateRotate(mLeft, mCurrent, mRight, effectOffset, true);
            break;
        case RotateDown:
            animateRotate(mLeft, mCurrent, mRight, effectOffset, false);
            break;
        case Accordion:
            animateAccordion(mLeft, mCurrent, mRight, effectOffset);
            break;
        }

        if (effectOffset == 0) {
            mState = State.IDLE;
        }
    }

    private boolean isSmall(float positionOffset) {
        return Math.abs(positionOffset) < 0.0001;
    }

    /**
     * setObjectForPosition
     * @param obj item
     * @param position index
     */
    public void setObjectForPosition(Object obj, int position) {
        mObjs.put(position, obj);
    }

    /**
     * gets the item object
     * @param position index
     * @return item
     */
    public Component findViewFromObject(int position) {
        Object obj = mObjs.get(position);
        if (obj == null) {
            return null;
        }
        return (Component) obj;
    }
}